﻿namespace System.ServiceModel.DomainServices.Client
{
    using System;
    using System.Collections;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;
    using System.Globalization;
    using System.Linq;
    using System.Reflection;

    public sealed class EntityRef<TEntity> : IEntityRef where TEntity: System.ServiceModel.DomainServices.Client.Entity
    {
        private AssociationAttribute _assocAttribute;
        private TEntity _entity;
        private Func<TEntity, bool> _entityPredicate;
        private bool _hasAssignedEntity;
        private bool _hasLoadedEntity;
        private bool _isComposition;
        private string _memberName;
        private System.ServiceModel.DomainServices.Client.Entity _parent;
        private EntitySet _sourceSet;

        public EntityRef(System.ServiceModel.DomainServices.Client.Entity parent, string memberName, Func<TEntity, bool> entityPredicate)
        {
            if (parent == null)
            {
                throw new ArgumentNullException("parent");
            }
            if (string.IsNullOrEmpty(memberName))
            {
                throw new ArgumentNullException("memberName");
            }
            if (entityPredicate == null)
            {
                throw new ArgumentNullException("entityPredicate");
            }
            this._parent = parent;
            this._entityPredicate = entityPredicate;
            this._memberName = memberName;
            PropertyInfo property = this._parent.GetType().GetProperty(memberName, MetaType.MemberBindingFlags);
            if (property == null)
            {
                throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resource.Property_Does_Not_Exist, new object[] { parent.GetType(), memberName }), "memberName");
            }
            this._assocAttribute = property.GetCustomAttributes(false).OfType<AssociationAttribute>().SingleOrDefault<AssociationAttribute>();
            if (this._assocAttribute == null)
            {
                throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resource.MemberMustBeAssociation, new object[] { memberName }), "memberName");
            }
            this._isComposition = property.GetCustomAttributes(typeof(CompositionAttribute), false).Any<object>();
            this._parent.RegisterSetChangedCallback(new Action(this.OnEntitySetChanged));
            this._parent.SetEntityRef(memberName, this);
            this._parent.PropertyChanged += new PropertyChangedEventHandler(this.ParentEntityPropertyChanged);
        }

        private bool Filter(TEntity entity)
        {
            if (entity.EntityState != EntityState.New)
            {
                return this._entityPredicate(entity);
            }
            return false;
        }

        private TEntity GetSingleMatch(IEnumerable entities)
        {
            TEntity local = default(TEntity);
            foreach (TEntity local2 in Enumerable.Where<TEntity>(entities.OfType<TEntity>(), new Func<TEntity, bool>(this.Filter)))
            {
                if (local != null)
                {
                    return default(TEntity);
                }
                local = local2;
            }
            return local;
        }

        private void MonitorEntitySet()
        {
            if (this._parent.EntitySet != null)
            {
                if (this._hasLoadedEntity || this._hasAssignedEntity)
                {
                    if (this._sourceSet != null)
                    {
                        this._sourceSet.CollectionChanged -= new NotifyCollectionChangedEventHandler(this.SourceSet_CollectionChanged);
                        this._sourceSet.RegisterAssociationCallback(this._assocAttribute, new Action<System.ServiceModel.DomainServices.Client.Entity>(this.OnEntityAssociationUpdated), false);
                    }
                    this._sourceSet = this._parent.EntitySet.EntityContainer.GetEntitySet(typeof(TEntity));
                    this._sourceSet.CollectionChanged += new NotifyCollectionChangedEventHandler(this.SourceSet_CollectionChanged);
                    this._sourceSet.RegisterAssociationCallback(this._assocAttribute, new Action<System.ServiceModel.DomainServices.Client.Entity>(this.OnEntityAssociationUpdated), true);
                }
            }
            else if ((this._parent.EntitySet == null) && (this._sourceSet != null))
            {
                this._sourceSet.CollectionChanged -= new NotifyCollectionChangedEventHandler(this.SourceSet_CollectionChanged);
                this._sourceSet.RegisterAssociationCallback(this._assocAttribute, new Action<System.ServiceModel.DomainServices.Client.Entity>(this.OnEntityAssociationUpdated), false);
                this._sourceSet = null;
            }
        }

        private void OnEntityAssociationUpdated(System.ServiceModel.DomainServices.Client.Entity entity)
        {
            if ((entity.EntityState != EntityState.New) || !entity.IsMergingState)
            {
                TEntity local = entity as TEntity;
                if ((local != null) && (this._hasLoadedEntity || this._hasAssignedEntity))
                {
                    if (((this._entity != local) && (this._parent.EntityState != EntityState.New)) && this.Filter(local))
                    {
                        this.SetValue(local);
                    }
                    else if ((this._entity == local) && !this._entityPredicate(local))
                    {
                        this.SetValue(default(TEntity));
                    }
                }
            }
        }

        private void OnEntitySetChanged()
        {
            this.MonitorEntitySet();
        }

        private void ParentEntityPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if ((this._hasLoadedEntity || this._hasAssignedEntity) && this._assocAttribute.ThisKeyMembers.Contains<string>(e.PropertyName))
            {
                this._entity = default(TEntity);
                this._hasAssignedEntity = false;
                this._hasLoadedEntity = false;
                this._parent.RaisePropertyChanged(this._memberName);
            }
        }

        private void SetValue(TEntity value)
        {
            if (this._entity != value)
            {
                this._entity = value;
                if ((this._entity != null) && this._isComposition)
                {
                    this._entity.SetParent(this._parent, this._assocAttribute);
                }
                this._parent.RaisePropertyChanged(this._memberName);
            }
        }

        private void SourceSet_CollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
        {
            if ((this._parent.EntityState != EntityState.New) && (args.Action == NotifyCollectionChangedAction.Add))
            {
                TEntity singleMatch = this.GetSingleMatch(args.NewItems);
                if ((singleMatch != null) && (singleMatch != this._entity))
                {
                    this.SetValue(singleMatch);
                }
            }
            else if (args.Action == NotifyCollectionChangedAction.Remove)
            {
                if ((this._entity != null) && args.OldItems.OfType<TEntity>().Contains<TEntity>(this._entity))
                {
                    this.SetValue(default(TEntity));
                }
            }
            else if ((args.Action == NotifyCollectionChangedAction.Reset) && (this._entity != null))
            {
                this.SetValue(default(TEntity));
            }
        }

        public TEntity Entity
        {
            get
            {
                if (!this._hasAssignedEntity && ((this._entity == null) || !this._entityPredicate(this._entity)))
                {
                    this._entity = default(TEntity);
                    if (this._parent.EntitySet != null)
                    {
                        EntitySet entitySet = this._parent.EntitySet.EntityContainer.GetEntitySet(typeof(TEntity));
                        this._entity = this.GetSingleMatch(entitySet);
                        if ((this._entity != null) && this._isComposition)
                        {
                            this._entity.SetParent(this._parent, this._assocAttribute);
                        }
                        this._hasLoadedEntity = true;
                        this.MonitorEntitySet();
                    }
                }
                return this._entity;
            }
            set
            {
                if ((value != null) && (this.SourceSet != null))
                {
                    this.SourceSet.EntityContainer.CheckCrossContainer(value);
                }
                bool flag = this._entity != value;
                if ((this.SourceSet != null) && (value != null))
                {
                    if (!this.SourceSet.IsAttached(value))
                    {
                        value.IsInferred = true;
                        this.SourceSet.Add(value);
                    }
                    else if (this._isComposition && (value.EntityState == EntityState.Deleted))
                    {
                        this.SourceSet.Add(value);
                    }
                }
                if (this._isComposition && flag)
                {
                    if (value != null)
                    {
                        value.SetParent(this._parent, this._assocAttribute);
                    }
                    else if ((this._sourceSet != null) && this._sourceSet.IsAttached(this._entity))
                    {
                        this._sourceSet.Remove(this._entity);
                    }
                    this._parent.OnChildUpdate();
                }
                this._hasAssignedEntity = true;
                this._entity = value;
            }
        }

        private EntitySet SourceSet
        {
            get
            {
                if (this._parent.EntitySet != null)
                {
                    this._sourceSet = this._parent.EntitySet.EntityContainer.GetEntitySet(typeof(TEntity));
                }
                return this._sourceSet;
            }
        }

        AssociationAttribute IEntityRef.Association
        {
            get
            {
                return this._assocAttribute;
            }
        }

        System.ServiceModel.DomainServices.Client.Entity IEntityRef.Entity
        {
            get
            {
                return this.Entity;
            }
        }

        Func<System.ServiceModel.DomainServices.Client.Entity, bool> IEntityRef.Filter
        {
            get
            {
                return e => base._entityPredicate((TEntity) e);
            }
        }

        bool IEntityRef.HasValue
        {
            get
            {
                return (this._entity != null);
            }
        }
    }
}

